#include "StdAfx.h"
#include "gp_uv.h"
#include "module_specific.h"
//#include "boost\lexical_cast.hpp"
#include "boost\shared_ptr.hpp"
#include "boost\lexical_cast.hpp"
#include "boost\foreach.hpp"
#include "shared\pd_errors.h"
//#include <lib\shared_lib\pugixml.hpp>
#include <lib\rle_lib\BitmapExData.h>
#include <lib\rle_lib\BitmapExFunc.h>
#include "shared\struct_extraction.h"
#include "shared\debug.h"
#include "core\modHelper\UtfConverter.h"

#include "GraphicPluginImpl.h"

using namespace std;
using namespace boost;
using namespace pugi;

const char *PLANE_UV = "uv";

GraphicPluginImpl::GraphicPluginImpl(void)
{
}

GraphicPluginImpl::~GraphicPluginImpl(void)
{
}

result GraphicPluginImpl::initGraphicPlugin( IN const char *pszInput, OUT std::string &strPluginInfo )
{
	xml_document Doc;
	xml_node Node = Doc.set("graphicplugin");
	Node.set( ( string( "name=" ) + PLUGIN_MODULE_PREFIX ).c_str() );
	Node.set("version=1.0");
	Node.set("requiredgmversion=0.9");
	Node = Node.set("planes");
	Node.set( ( string( "+plane.name=" ) + PLUGIN_MODULE_PREFIX ).c_str() );

	strPluginInfo = Doc.print();

	initPlaneInfo();

	pUtfConverter_ = g_pModuleMgr->getModule<UtfConverter>( UTF_CONVERTER_MODULE_NAME );

//	pHardwareStatusQuery_ = g_pModuleMgr->getModule<HardwareStatusQuery>( XID5LAN_HARDWARE_STATUS_QUERY_MODULE_NAME_BASE );


	return RES_OK;
}

result GraphicPluginImpl::queryPlaneInfo(
	IN const char *pszPlane,
	OUT std::string &strPlaneInfo )
{
	bool PlaneSupported = false;

	xml_document PlaneInfo;

	PlaneInfo.append_child().set_name("planeinfo");
	xml_node node = PlaneInfo.child("planeinfo");

	std::map<std::string,_PlaneInfo>::const_iterator itPlaneInfo =
		PlaneInfoMap_.find( pszPlane );
	if ( itPlaneInfo != PlaneInfoMap_.end() )
	{
		convertPlaneInfoToNode( itPlaneInfo->second, node );
		strPlaneInfo = PlaneInfo.print();
		PlaneSupported = true;
	}

	return PlaneSupported ? RES_OK : RES_PARAM_ERROR;
}

result GraphicPluginImpl::startPage( 
	IN jobid_t ProductionJobId, 
	IN ProductionJobData &pjd,
	IN const char *pszPlane,
	INOUT CBitmapExData &PlaneBitmap )
{
	JobMap_t::iterator itJob = JobMap_.find( ProductionJobId );

	if ( itJob != JobMap_.end() )
	{
		itJob->second.pPjd = &pjd;
	} else {
		_GpJobData JobData;
		JobData.pPjd = &pjd;
		JobData.BitmapLoaded[ ProductionJobData::SIDE_FRONT ] = false;
		JobData.BitmapLoaded[ ProductionJobData::SIDE_BACK ] = false;
		JobMap_[ ProductionJobId ] = JobData;
	}

	return RES_OK;
}

result GraphicPluginImpl::processArea( 
	IN jobid_t ProductionJobId, 
	IN ExtractionObject &Object,
	IN const char *pszArea,
	INOUT CBitmapExData &PlaneBitmap,
	OUT std::string &strResult )
{
	result Success = RES_OK;

	xml_document DocArea;
	if ( DocArea.load( pszArea ) )
	{
		if ( Object.Sequence == ExtractionArea::SEQUENCE_PRE )
		{
			Success = processPre( ProductionJobId,
								  DocArea,
								  Object,
								  PlaneBitmap,
								  strResult )
				? RES_OK : RES_ERROR_FAILURE;
		}
/*
		else if ( Object.Sequence = ExtractionArea::SEQUENCE_POST )
		{
			Success = processPost( ProductionJobId,
				DocArea,
				Object,
				PlaneBitmap )
				? RES_OK : RES_ERROR_FAILURE;
			strResult = "<result copytocmy=\"true\" />"; // not copy, but "leave" CMY area untouched
		}
*/
	}

	return Success;
}

result GraphicPluginImpl::endDocument( IN jobid_t ProductionJobId )
{
	JobMap_t::iterator itPjd = JobMap_.find( ProductionJobId );

	if ( itPjd != JobMap_.end() )
	{
		JobMap_.erase( itPjd );
	}

	return RES_OK;
}

void GraphicPluginImpl::convertPlaneInfoToNode( IN const _PlaneInfo &pi, OUT pugi::xml_node Node )
{
	string strTemp = "bitspercolor=" + lexical_cast<string>( pi.BitsPerColor );
	Node.set( strTemp.c_str() );

	strTemp = "bitsperplanepixel=" + lexical_cast<string>( pi.BitsPerPlanePixel );
	Node.set( strTemp.c_str() );

	strTemp = "colororder=" + pi.strColorOrder;
	Node.set( strTemp.c_str() );

	strTemp = "channelorder=" + pi.strChannelOrder;
	Node.set( strTemp.c_str() );

	strTemp = "width=" + lexical_cast<string>( pi.Width );
	Node.set( strTemp.c_str() );

	strTemp = "height=" + lexical_cast<string>( pi.Height );
	Node.set( strTemp.c_str() );

	strTemp = "defaultorientation=" + pi.strDefaultOrientation;
	Node.set( strTemp.c_str() );

	strTemp = "flipaxis=" + pi.strFlipAxis;
	Node.set( strTemp.c_str() );

	strTemp = "fliphorizontal=";
	strTemp.append( pi.FlipHorizontal ? "true" : "false" );
	Node.set( strTemp.c_str() );

	strTemp = "flipvertical=";
	strTemp.append( pi.FlipVertical ? "true" : "false" );
	Node.set( strTemp.c_str() );

	strTemp = "invertcolors=";
	strTemp.append( pi.InvertColors ? "true" : "false" );
	Node.set( strTemp.c_str() );

	strTemp = "drawingplane=" + pi.strDrawingPlane;
	Node.set( strTemp.c_str() );

	strTemp = "backgroundcolor=" + lexical_cast<string>( pi.BackgroundColor );
	Node.set( strTemp.c_str() );
}

void GraphicPluginImpl::initPlaneInfo()
{
		_PlaneInfo pi;

		pi.BitsPerColor = 8;
		pi.BitsPerPlanePixel = 8;
//		pi.strColorOrder // we have no channel order for uv
//		pi.strChannelOrder // see above
		pi.Width = 1036;
		pi.Height = 664;
		pi.strDefaultOrientation = "landscape";
		pi.strFlipAxis = "long";
		pi.FlipHorizontal = true;
		pi.FlipVertical = false;
		pi.InvertColors = false;
		pi.strDrawingPlane = PLANE_UV;
		pi.BackgroundColor = 0;
		PlaneInfoMap_[ PLANE_UV ] = pi;
}

result GraphicPluginImpl::preprocessAreas( IN jobid_t ProductionJobId,
										   IN const char *pszPlane,
										   IN const char *pszProfile,
										   INOUT ExtractionAreas &AllAreas )
{
	xml_document DocProfile;

	limitAreas( AllAreas );

	// check "bitmap"
	if ( DocProfile.load( pszProfile ) )
	{
		xml_node NodeUv = DocProfile.getNode( "printerprofile.printingpreferences.uv" );
		if ( NodeUv )
		{
//			_GpJobData JobData;
			bool BitmapAdded = false;
			// check if there is any "fromsecondpage" on each side
			for ( size_t Side = ProductionJobData::SIDE_FRONT; Side <= ProductionJobData::SIDE_BACK; ++Side )
			{

				string strBitmapText = production_job_data_tools::convertSideToString( 
					static_cast<ProductionJobData::CardSide>( Side ) ).append( ".bitmap" );
				string strBitmapFile = NodeUv.getString( strBitmapText.c_str() );
				if ( !strBitmapFile.empty() )
				{
/*
					JobData.strBitmapFile[ Side ] = strBitmapFile;
					BitmapAdded = true;
*/
					addUvBitmap( AllAreas.Side[ Side ], strBitmapFile.c_str() );
				}
			}

/*
			if ( BitmapAdded )
			{
				ASSERTR( JobMap_.find( ProductionJobId ) == JobMap_.end() );
				JobMap_[ ProductionJobId ] = JobData;
			}
*/
		}
	}

	return RES_OK;
}

bool GraphicPluginImpl::processPre( IN jobid_t ProductionJobId,
									IN pugi::xml_document &DocArea, 
									IN ExtractionObject &Object,
									INOUT CBitmapExData &DstBmpData,
									OUT std::string &strResult )
{
	CBitmapExData &SrcBmpData( *Object.pBmpData );

	if ( DocArea.getString( "area.type" ) == "bitmap" )
	{
		JobMap_t::iterator itJob = JobMap_.find( ProductionJobId );
		ASSERTR( itJob != JobMap_.end() );

		if ( !itJob->second.BitmapLoaded[ Object.Side ] )
		{
			CBitmapExData UvBitmapData;
			CBitmapExFunc UvBitmap( &UvBitmapData );
			UvBitmap.Load( pUtfConverter_->toUtf16( DocArea.getString( "area.bitmap" ).c_str() ).c_str() );

			CBitmapExFunc UvPlane( &DstBmpData );
			UvPlane.SetResampleMode( RM_BILINEAR );
			UvPlane.Draw( 0, 0, DstBmpData.bih.biWidth - 1, DstBmpData.bih.biHeight - 1, 
				UvBitmap, 0, 0, UvBitmapData.bih.biWidth, UvBitmapData.bih.biHeight );
			itJob->second.BitmapLoaded[ Object.Side ] = true;
		}
		strResult = "<result copytocmy=\"true\" />";
	} else {
		POINT Offset = DocArea.getPoint( "area.offset" );

		CBitmapExFunc SrcBmp( &SrcBmpData ),
			DstBmp( &DstBmpData );

		// replace pure black text in source bitmap
		if ( DocArea.getString( "area.type" ) == "text" )
		{
			size_t Size = SrcBmpData.Pitch * SrcBmpData.bih.biHeight / sizeof( boost::uint32_t );
			boost::uint32_t *pSrc = reinterpret_cast<boost::uint32_t *>( SrcBmpData.getData() ),
				val;

			while ( Size-- )
			{
				val = *pSrc;
				if ( ( val & 0xffffff ) == _RGB( 0, 0, 0 ) )
				{
					*pSrc |= _RGB( 255, 255, 255 );
				}
				++pSrc;
			}
		}

		DstBmp.DrawTransparent( Object.BoundingBox.left + Offset.x, 
								Object.BoundingBox.top + Offset.y, 
								Object.BoundingBox.right - Object.BoundingBox.left,
								Object.BoundingBox.bottom - Object.BoundingBox.top, 
								SrcBmp,
								Object.BoundingBox.left, 
								Object.BoundingBox.top,
								static_cast<_PIXEL>( 0xffffffff ) );
		strResult.clear();
	}

	return true;
}

void GraphicPluginImpl::addUvBitmap( INOUT ExtractionAreas_t &AllAreas, IN const char *pszBitmapFile )
{
	ExtractionArea Area;
	Area.Sequence = ExtractionArea::SEQUENCE_PRE;
	Area.Type = ExtractionArea::TYPE_ANY;
	Area.strDstPlane = PLANE_UV;
	Area.Rect.left = 0;
	Area.Rect.top = 0;
	Area.Rect.right = PlaneInfoMap_[ PLANE_UV ].Width - 1;
	Area.Rect.bottom = PlaneInfoMap_[ PLANE_UV ].Height - 1;

	Area.strArea = "<area rect=\"";
	Area.strArea.append( lexical_cast<string>( Area.Rect.left ) ).append( "," );
	Area.strArea.append( lexical_cast<string>( Area.Rect.top ) ).append( "," );
	Area.strArea.append( lexical_cast<string>( Area.Rect.right ) ).append( "," );
	Area.strArea.append( lexical_cast<string>( Area.Rect.bottom ) ).append( "\" sequence=\"pre\" type=\"bitmap\" bitmap=\"" ).append( pszBitmapFile ).append( "\" />" );
	AllAreas.push_front( Area );
}

/*
bool GraphicPluginImpl::processPost( IN jobid_t ProductionJobId, 
									 IN pugi::xml_document &DocArea, 
									 IN ExtractionObject &Object, 
									 INOUT CBitmapExData &DstBmpData )
{
	return RES_OK;
}
*/

void GraphicPluginImpl::limitAreas( INOUT ExtractionAreas &AllAreas )
{
	for ( size_t Side = ProductionJobData::SIDE_FRONT; Side <= ProductionJobData::SIDE_BACK; ++Side )
	{
		BOOST_FOREACH( ExtractionArea &Area, AllAreas.Side[ Side ] )
		{
			std::map<std::string,_PlaneInfo>::const_iterator itPlaneInfo =
				PlaneInfoMap_.find( Area.strDstPlane );

			if ( itPlaneInfo != PlaneInfoMap_.end() )
			{
				int SideLength = max( itPlaneInfo->second.Width, itPlaneInfo->second.Height ) - 1;
				Area.Rect.left   = max( 0, min( SideLength, Area.Rect.left  ) );
				Area.Rect.top    = max( 0, min( SideLength, Area.Rect.top   ) );
				Area.Rect.right  = max( 0, min( SideLength, Area.Rect.right  ) );
				Area.Rect.bottom = max( 0, min( SideLength, Area.Rect.bottom ) );

				if ( Area.Rect.left > Area.Rect.right ) swap( Area.Rect.left, Area.Rect.right );
				if ( Area.Rect.top > Area.Rect.bottom ) swap( Area.Rect.top , Area.Rect.bottom );
			}
		}
	}
}
